package com.example.spider.proxy;

import cn.hutool.core.util.HashUtil;
import com.example.spider.event.DropProxyEvent;
import com.example.spider.proxy.core.ProxyInfo;
import com.example.spider.proxy.core.ProxyRepository;
import com.example.spider.proxy.core.tester.ProxyTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.net.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;

@Slf4j
@Component
public class ProxyPool implements ApplicationListener<ContextRefreshedEvent> {

    @Lazy
    @Autowired
    @Qualifier(ProxyRepository.STABLE)
    private ProxyRepository stableRepository;

    /**
     * 取的时候从这里复制，然后再取 {@link #getProxyArray}
     * key(ip:port) -> value(ipProxyInfo)
     * key(score) -> value(ipProxyInfo)
     */
    private final ConcurrentSkipListSet<ProxyInfo> readOnlyProxies = new ConcurrentSkipListSet<>();

    public static final ProxyInfo NO_PROXY = new ProxyInfo().setIp("127.0.0.1")
            .setProxy(Proxy.NO_PROXY)
            .setProxyTypeEnum(ProxyTypeEnum.DIRECT)
            .setSupportHttps(true);


    // ==========================================================
    @EventListener(DropProxyEvent.class)
    public void onDropProxyEvent(DropProxyEvent event) {
        readOnlyProxies.remove(event.getTask().getActuallyProxy());
    }

    /**
     * 定期加载
     */
    @Scheduled(cron = "0 */9 * * * ?")
    public void loadFromStableRepo() {
        Iterator<ProxyInfo> it = stableRepository.getIterator();
        while (it.hasNext()) {
            readOnlyProxies.add(it.next());
        }
    }

    /**
     * 定期remove差的
     */
    @Scheduled(cron = "0 */4 * * * ?")
    public void removeBadProxy() {
        removeIf(proxyInfo -> proxyInfo.getScore() < -20);
    }

    /**
     * ******************** 获取用 ********************************
     */

    public ProxyInfo getUseHash(String source, boolean withWeight, Function<ProxyInfo, Boolean> filter) {
        Object[] proxyInfoArr = getProxyArray(withWeight, filter);
        if (proxyInfoArr.length == 0) {
            return null;
        }
        int index = HashUtil.rotatingHash(source, proxyInfoArr.length);
        return ((ProxyInfo) proxyInfoArr[index]);
    }

    public ProxyInfo getRandomProxy(boolean withWeight, Function<ProxyInfo, Boolean> filter) {
        Object[] proxyInfoArr = getProxyArray(withWeight, filter);
        if (proxyInfoArr.length == 0) {
            return null;
        }
        int size = proxyInfoArr.length;
        int num = ThreadLocalRandom.current().nextInt(size);
        return ((ProxyInfo) proxyInfoArr[num]);
    }

    public ProxyInfo getNearByIndex(AtomicInteger atomicInteger, boolean withWeight, Function<ProxyInfo, Boolean> filter) {
        Object[] proxyInfoArr = getProxyArray(withWeight, filter);
        if (proxyInfoArr.length == 0) {
            return null;
        }
        return getNearByIndex(proxyInfoArr, atomicInteger, filter);
    }

    // --------------------------------------------------------

    private ProxyInfo getNearByIndex(Object[] proxyInfos, AtomicInteger atomicInteger, Function<ProxyInfo, Boolean> filter) {
        int size = proxyInfos.length;
        int index = atomicInteger.getAndIncrement();
        if (index >= size) {
            // 一轮了
            atomicInteger.set(0);
            return ((ProxyInfo) proxyInfos[0]);
        } else {
            return ((ProxyInfo) proxyInfos[index]);
        }
    }

    /**
     * 返回的是当前这一刻全部代理的快照
     * clone 的是引用，不会浪费性能
     *
     * @param withWeight 是否带权重，带权重则会有重复的
     * @param filter
     * @return 当前全部代理
     */
    private Object[] getProxyArray(boolean withWeight, Function<ProxyInfo, Boolean> filter) {
        //
        Object[] proxyInfoArr = readOnlyProxies.toArray();
        if (!withWeight) {
            return proxyInfoArr;
        }
        List<ProxyInfo> proxyInfos = new ArrayList<>(proxyInfoArr.length << 1);
        for (Object o : proxyInfoArr) {
            ProxyInfo proxyInfo = (ProxyInfo) o;
            if (!filter.apply(proxyInfo)) {
                continue;
            }
            int score = proxyInfo.getScore();
            if (score > 0) {
                proxyInfos.add(proxyInfo);
                for (int i = 0; i < score / 20; i++) {
                    proxyInfos.add(proxyInfo);
                }
            }
        }
        return proxyInfos.toArray();
    }

    /**
     * ******************** 动态写入用 ********************************
     */

    public void addProxyInfos(Collection<? extends ProxyInfo> proxyInfos) {
        readOnlyProxies.addAll(proxyInfos);
    }

    public ProxyInfo remove(String ip, int port) {
        for (ProxyInfo proxyInfo : readOnlyProxies) {
            if (proxyInfo.getIp().equals(ip) && proxyInfo.getPort() == port) {
                readOnlyProxies.remove(proxyInfo);
                return proxyInfo;
            }
        }
        return null;
    }

    public ProxyInfo remove(ProxyInfo proxyInfo) {
        return remove(proxyInfo.getIp(), proxyInfo.getPort());
    }

    /**
     * 删除代理
     *
     * @param filter 内部方法带锁，因此不建议filter 执行耗时操作（如测试proxy）
     */
    public boolean removeIf(Predicate<? super ProxyInfo> filter) {
        return readOnlyProxies.removeIf(filter);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 可异步
        loadFromStableRepo();
    }
}
